package com.imooc.bilibili.util;

import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.imooc.bilibili.constant.fdfs.FdfsConstant;
import com.imooc.bilibili.exception.ConditionException;
import com.imooc.bilibili.util.redis.RedisUtils;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.concurrent.locks.Condition;

/**
 * FastDFS工具类
 *
 * @author gcq
 * @Create 2022-06-03
 */
@Component
public class FastDFSUtil {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Autowired
    private AppendFileStorageClient appendFileStorageClient;

    @Autowired
    private RedisUtils redisUtils;

    private static final String FAST_GROUP = "group1";

    @Value("${fdfs.http.storage-addr}")
    private String storageAddress;

    /**
     * 获取文件类型
     *
     * @param file
     * @return
     */
    public String getFileType(MultipartFile file) {
        if(file == null) {
            throw new ConditionException("非法文件");
        }
        String fileName = file.getOriginalFilename();
        int index = fileName.lastIndexOf(".");
        return fileName.substring(index + 1);
    }


    /**
     * 上传文件
     *
     * @param file
     * @return 文件名称
     * @throws IOException
     */
    public String uploadCommonFile(MultipartFile file) throws IOException {
        Set<MetaData> metaDataSet = new HashSet<>();
        String fileType = getFileType(file);
        StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), fileType, metaDataSet);
        return storePath.getPath();
    }

    public String uploadCommonFile(File file, String fileType) throws Exception {
        Set<MetaData> metaDataSet = new HashSet<>();
        StorePath storePath = fastFileStorageClient.uploadFile(new FileInputStream(file),
                file.length(), fileType, metaDataSet);
        return storePath.getPath();
    }

    /**
     * 删除文件
     *
     * @param filePath
     */
    public void deleteFile(String filePath) {
        fastFileStorageClient.deleteFile(filePath);
    }

    /**
     * 文件上传痛点
     * 大文件上传，上传过程中网络中断
     *
     * 断点续传
     * 大文件分片，一个分片2m~3m
     * 出现断点的话，之前删传的分片就不需要要重新上传
     * 例子：
     * 一个文件分成10个分片，上传五个后，网络中断，再次进行上传，之前五个已上传的分片不用进行上传
     */

    /**
     * 断点续传
     *
     * @param file
     * @return
     * @throws IOException
     */
    public String uploadAppenderFile(MultipartFile file) throws IOException {
        String fileName = file.getOriginalFilename();
        String fileType = getFileType(file);
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(FAST_GROUP, file.getInputStream(), file.getSize(), fileType);
        return storePath.getPath();
    }

    /**
     * 修改文件
     *
     * @param file
     * @param filePath
     * @param offset 文件偏移量
     * @throws IOException
     */
    public void modifyAppenderFile(MultipartFile file, String filePath, long offset) throws IOException {
        appendFileStorageClient.modifyFile(FAST_GROUP, filePath, file.getInputStream(), file.getSize(), offset);
    }


    /**
     * 上传路径
     */
    private static final String PATH_KEY = "path-key:";
    /**
     * 当前已上传分片总大小
     */
    private static final String UPLOADED_SIZE_KEY = "uploaded-size-key";
    /**
     * 记录已经上传多少个分片
     */
    private static final String UPLOADED_NO_KEY = "uploaded-no-key";
    /**
     * 分片大小
     */
    private static final Integer SLICE_SIZE = 1024 * 1024;

    /**
     *
     * fileMd5 将上传文件通过对应算法能计算出一个md5，上传前先查看是否存在该md5，已存在则不需要进行上传，【秒传】
     *
     * 分片上传
     * 首次上传，将当前文件地址，当前分片数量、当前文件大小，都存进redis中
     * 第二次进行上传时，取出对应的上传路径，文件大小，进行续传
     * 每次上传成功后，都需要更新当前文件大小以及分片数量，同步至redis中
     *
     * 当分片数量与总数量一致，则是上传成功，返回文件地址
     *
     * 【分片】 前端进行分片上传
     *
     * @param file 上传文件
     * @param fileMd5 文件md5
     * @param sliceNo 当前需上传分页
     * @param totalSliceNo 总分页数量
     * @return
     */
    public String uploadFileBySlices(MultipartFile file, String fileMd5, Integer sliceNo, Integer totalSliceNo) throws IOException {
        if(file == null || sliceNo == null || totalSliceNo == null) {
            throw new ConditionException("参数异常");
        }
        String pathKey = PATH_KEY + fileMd5;
        String uploadedSizeKey = UPLOADED_SIZE_KEY + fileMd5;
        String uploadedNoKey = UPLOADED_NO_KEY + fileMd5;
        String uploadedSizeKeyStr = redisUtils.get(uploadedSizeKey);
        // 获取已上传分片文件大小
        Long uploadedSize = 0L;
        if(StringUtils.isNotEmpty(uploadedSizeKeyStr)) {
            uploadedSize = Long.valueOf(uploadedSizeKeyStr);
        }

        if(sliceNo == 1) { // 当前第一个分片
            String filePath = uploadAppenderFile(file);
            if(StringUtils.isEmpty(filePath)) {
                throw new ConditionException("上传失败!");
            }
            // 设置返回路径，以及分片数量
            redisUtils.set(pathKey, filePath);
            redisUtils.set(uploadedNoKey, "1");
        } else { // 第二个或者往后多个分片
            String filePath = redisUtils.get(pathKey);
            if(StringUtils.isEmpty(filePath)) {
                throw new ConditionException("上传失败!");
            }
            // 续传文件
            modifyAppenderFile(file, filePath, uploadedSize);
            // 增加分片数量
            redisUtils.incr(uploadedNoKey);
        }
        // 修改历史上传分片文件大小
        uploadedSize += file.getSize();
        redisUtils.set(uploadedSizeKey, String.valueOf(uploadedSize));
        // 全部分片上传成功，清除redis中所有key
        String uploadedNoStr = redisUtils.get(uploadedNoKey);
        Integer uploadedNo = Integer.valueOf(uploadedNoStr);
        String filePath = "";
        if(uploadedNo.equals(totalSliceNo)) {
            filePath = redisUtils.get(pathKey);
            List<String> keyList = Arrays.asList(pathKey, uploadedSizeKey, uploadedNoKey);
            redisUtils.del(keyList);
        }
        return filePath;
    }

    public void convertFileToSlices(MultipartFile multipartFile) throws IOException {
        String fileName = multipartFile.getOriginalFilename();
        String fileType = getFileType(multipartFile);
        File file = multipartFileToFile(multipartFile);
        long fileLength = file.length();
        int count = 1;
        for (int i = 0; i < fileLength; i += SLICE_SIZE) {
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            // 文件偏移量
            randomAccessFile.seek(i);
            // 指定大小
            byte[] bytes = new byte[SLICE_SIZE];
            // 读取指定字节放到字节数组中，并返回字节数
            int len = randomAccessFile.read(bytes);
            // 创建一个临时目录用来存放
            // 多个文件上传
            String path = "D:\\workspaces\\tempFile\\" + count + "." + fileType;
            File slice = new File(path);
            FileOutputStream fos = new FileOutputStream(slice);
            // 字节写入，
            fos.write(bytes, 0, len);
            fos.close();
            randomAccessFile.close();
            count ++;
        }
        // 临时文件删除
        file.delete();
    }

    /**
     * 将 multipartFile 转换成 File
     *
     * @param multipartFile 多个文件
     * @return
     * @throws IOException
     */
    private File multipartFileToFile(MultipartFile multipartFile) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename();
        String[] fileNames = originalFilename.split("\\.");
        // 前缀以及后缀
        File file = File.createTempFile(fileNames[0], "." + fileNames[1]);
        // 转换成 File
        multipartFile.transferTo(file);
        return file;
    }

    /**
     * 请求视频信息
     *
     * @param request
     * @param response
     * @param path
     * @throws Exception
     */
    public void viewVideoOnlineBySlices(HttpServletRequest request,
                                        HttpServletResponse response,
                                        String path) throws Exception {
        FileInfo fileInfo = fastFileStorageClient.queryFileInfo(FdfsConstant.DEFAULT_GROUP, path);
        long totalFileSize = fileInfo.getFileSize();
        String url = storageAddress + path;
        // 获取请求头所有信息
        Enumeration<String> headerNames = request.getHeaderNames();
        // 将当前请求所有请求头信息添加到map中
        Map<String, Object> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String header = headerNames.nextElement();
            headers.put(header, request.getHeader(header));
        }
        String rangeStr = request.getHeader("Range");
        String[] range;
        if(StringUtils.isEmpty(rangeStr)) {
            rangeStr = "bytes=0-" + (totalFileSize - 1);
        }
        // bytes=2222222-3333333333
        range = rangeStr.split("bytes=|-");
        long begin = 0;
        if(range.length >= 2){
            begin = Long.parseLong(range[1]);
        }
        long end = 0;
        if(range.length >= 3) {
            end = Long.parseLong(range[2]);
        }
        long len = (end - begin) + 1;
        // 设置请求信息
        String contentRange = "bytes " + begin + "-" + end + "/" + totalFileSize;
        // 请求范围
        response.setHeader("Content-Range", contentRange);
        // 范围
        response.setHeader("Accept-Ranges", "bytes");
        // 类型
        response.setHeader("Content-Type", "video/map4");
        response.setContentLength((int)len);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        HttpUtil.get(url, headers, response);
    }

    /**
     * 下载文件
     *
     * @param url
     * @param filePath
     */
    public void downloadFile(String url, String filePath) {
        fastFileStorageClient.downloadFile(FdfsConstant.DEFAULT_GROUP, url, new DownloadCallback<String>() {
            @Override
            public String recv(InputStream ins) throws IOException {
                File file = new File(filePath);
                OutputStream os = new FileOutputStream(file);
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = ins.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
                os.close();
                ins.close();
                return "success";
            }
        });
    }
}